//
//  sdl_queue.h
//  FFmpegDemo
//
//  Created by xiaerfei on 2019/5/9.
//  Copyright © 2019 erfeixia. All rights reserved.
//

#ifndef sdl_queue_h
#define sdl_queue_h

#include <stdio.h>
#include <SDL2/SDL.h>
#include <libavcodec/avcodec.h>


extern AVPacket flush_pkt;
#pragma mark - packet queue

typedef struct MyAVPacketList {
    AVPacket pkt; ///< 解封装后的数据
    struct MyAVPacketList *next; ///< 下一个节点
    int serial;///< 序列号 serial字段主要用于标记当前节点的序列号，ffplay中多处用到serial的概念，一般用于区分是否连续数据。
} MyAVPacketList;

typedef struct PacketQueue {
    MyAVPacketList *first_pkt, *last_pkt; ///< 队首，队尾
    int nb_packets;     ///< 队列中一共有多少个节点
    int size;           ///< 队列所有节点字节总数，用于计算cache大小
    int64_t duration;   ///< 队列所有节点的合计时长
    int abort_request;  ///< 是否要中止队列操作，用于安全快速退出播放
    int serial;         ///< 序列号，和MyAVPacketList的serial作用相同，但改变的时序稍微有点不同
    SDL_mutex *mutex;  ///< 用于维持PacketQueue的多线程安全(SDL_mutex可以按pthread_mutex_t理解）
    SDL_cond *cond;    ///< 用于读、写线程相互通知(SDL_cond可以按pthread_cond_t理解 信号量)
    SDL_cond *pkt_num_cond;
} PacketQueue;


/**
 队列的初始化

 @param q 队列指针
 @return 0 成功  其它失败
 */
int packet_queue_init(PacketQueue *q);
/**
 销毁队列

 @param q 队列指针
 */
void packet_queue_destroy(PacketQueue *q);
/**
 启用队列
 
 @param q 队列指针
 */
void packet_queue_start(PacketQueue *q);

/**
 终止队列

 @param q 队列指针
 */
void packet_queue_abort(PacketQueue *q);

/**
 向队列 q 中添加数据 pkt

 @param q 队列指针
 @param pkt pkt 数据
 @return 成功 > 0
 */
int packet_queue_put(PacketQueue *q, AVPacket *pkt);

/**
 向队列 q 获取数据
 
 @param q 队列指针
 @param pkt 要从队列里获取的数据
 @param block 调用者是否需要在没节点可取的情况下阻塞等待
 @param serial 输出参数，即MyAVPacketList.serial
 @return 成功 > 0
 */
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial);

/**
 清除队列内所有的节点

 @param q 队列指针
 */
void packet_queue_flush(PacketQueue *q);

int packet_queue_put_nullpacket(PacketQueue *q, int stream_index);

#pragma mark - frame queue

#define VIDEO_PICTURE_QUEUE_SIZE 3
#define SUBPICTURE_QUEUE_SIZE 16
#define SAMPLE_QUEUE_SIZE 9
#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))




typedef struct Frame {
    AVFrame *frame; ///< 视频或音频的解码数据
    AVSubtitle sub; ///< 解码的字幕数据
    int serial;
    double pts;           /* presentation timestamp for the frame */
    double duration;      /* estimated duration of the frame */
    int64_t pos;          /* byte position of the frame in the input file */
    int width;
    int height;
    int format;
    AVRational sar;
    int uploaded;
    int flip_v;
} Frame;
/*
 FrameQueue不是用链表实现队列，而是用数组实现队列（环形缓冲区）
 从字段的定义上可以看出，FrameQueue的设计显然比PacketQueue要复杂。在深入代码分析之前，先给出其设计理念：
 
 1.高效率的读写模型（回顾PacketQueue的设计，每次访问都需要加锁整个队列，锁范围很大）
 2.高效的内存模型（节点内存以数组形式预分配，无需动态分配）
 3.环形缓冲区设计，同时可以访问上一读节点
 
 
 */
typedef struct FrameQueue {
    Frame queue[FRAME_QUEUE_SIZE]; ///< 队列元素，用数组模拟队列
    int rindex;                      ///< 读指针
    int windex;                      ///< 写指针
    int size;                        ///< 当前存储的节点个数(或者说，当前已写入的节点个数)
    int max_size;                    ///< 最大允许存储的节点个数
    int keep_last;                   ///< 是否要保留最后一个读节点
    int rindex_shown;                ///< 当前节点是否已经显示
    SDL_mutex *mutex;
    SDL_cond *cond;
    PacketQueue *pktq;
} FrameQueue;


int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last);
Frame *frame_queue_peek_writable(FrameQueue *f);
void frame_queue_push(FrameQueue *f);
int frame_queue_nb_remaining(FrameQueue *f);
Frame *frame_queue_peek_last(FrameQueue *f);
Frame *frame_queue_peek(FrameQueue *f);
void frame_queue_next(FrameQueue *f);
Frame *frame_queue_peek_readable(FrameQueue *f);

#endif /* sdl_queue_h */
